#include "StdAfx.h"

#include <wx/app.h>
#include <wx/msgdlg.h>
#include <wx/menu.h>
#include <wx/sizer.h>
#include <wx/textctrl.h>
#include <wx/dirdlg.h>
#include <wx/settings.h>
#include <wx/stdpaths.h>
#include <wx/ipc.h>
#include <wx/cmdline.h>
#include <wx/log.h>

#include "yasli/TextIArchive.h"
#include "yasli/TextOArchive.h"
#include "yasli/Serializer.h"
#include "utils/Files.h"
#include "utils/MemoryWriter.h"

#include "MainFrame.h"
#include "ClassTree.h"
#include "TagStorage.h"
#include "TagSourceDirectory.h"

#include "property-tree/PropertyDialog.h"
#include "Preferences.h"

#include "EditorPreferences.h"
#include "TagPreferences.h"
#include "version.h"

class SymbolTextCtrl : public wxTextCtrl{
public:
	SymbolTextCtrl(wxWindow* parent, int id)
	: wxTextCtrl(parent, id, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER)
	{
	}
protected:

	void onSetFocus(wxFocusEvent& event)
	{
		event.Skip();
		SetSelection(-1, -1);
	}

	void onKeyDown(wxKeyEvent& event)
	{
		if(event.GetKeyCode() == WXK_DOWN || event.GetKeyCode() == WXK_TAB || event.GetKeyCode() == WXK_PAGEDOWN){
			wxKeyEvent newEvent = event;
			wxWindow* parent = GetParent();
			newEvent.SetEventObject(parent);
			newEvent.SetId(parent->GetId());
			parent->GetEventHandler()->ProcessEvent(newEvent);
		}
		else
			event.Skip();
	}

	DECLARE_EVENT_TABLE()

};

BEGIN_EVENT_TABLE(SymbolTextCtrl, wxTextCtrl)
	EVT_KEY_DOWN(SymbolTextCtrl::onKeyDown)
	EVT_SET_FOCUS(SymbolTextCtrl::onSetFocus)
END_EVENT_TABLE()


// ---------------------------------------------------------------------------

void handleException(ErrorGeneric& exception){
    MemoryWriter msg;
    msg << "Exception in " << exception.functionName()
        << "(), " << exception.fileName() << ": " << exception.line() << " line\n"; 
    msg << "\t" << exception.what();

    wxMessageBox(wxString(msg.c_str()),
                 wxT("Exception caughts"), wxOK | wxICON_ERROR);
}

enum {
    ID_FRAME = 10000,

	ID_SYMBOL_TEXT,
	ID_MEMBER_TEXT,
	ID_CLASS_TREE,

    // menus
    IDM_FILE_OPEN,
    IDM_FILE_OPEN_DIRECTORY,
    IDM_FILE_PREFERENCES,
    IDM_FILE_EXIT,

    IDM_HELP_ABOUT
};

IMPLEMENT_CLASS(MainFrame, wxFrame)
BEGIN_EVENT_TABLE(MainFrame, wxFrame)

    EVT_CLOSE(MainFrame::onClose)
    EVT_IDLE(MainFrame::onIdle)
	EVT_KEY_DOWN(MainFrame::onKeyDown)

	EVT_TIMER(wxID_ANY, MainFrame::onTimer)
	//
	EVT_TEXT(ID_SYMBOL_TEXT, MainFrame::onSymbolTextChanged)
	EVT_COMMAND(ID_SYMBOL_TEXT, wxEVT_COMMAND_TEXT_ENTER, MainFrame::onSymbolTextEnter)

	EVT_TEXT(ID_MEMBER_TEXT, MainFrame::onSymbolTextChanged)
	EVT_COMMAND(ID_MEMBER_TEXT, wxEVT_COMMAND_TEXT_ENTER, MainFrame::onSymbolTextEnter)

	EVT_TREE_ITEM_ACTIVATED(ID_CLASS_TREE, MainFrame::onSymbolSelected)
	///

    EVT_MENU(IDM_FILE_OPEN, MainFrame::onFileOpen)
    EVT_MENU(IDM_FILE_OPEN_DIRECTORY, MainFrame::onFileOpenDirectory)
    EVT_MENU(IDM_FILE_PREFERENCES, MainFrame::onFilePreferences)
    EVT_MENU(IDM_FILE_EXIT, MainFrame::onFileExit)

    EVT_MENU(IDM_HELP_ABOUT, MainFrame::onHelpAbout)

END_EVENT_TABLE()

void MainFrame::onKeyDown(wxKeyEvent& event)
{
	if(event.GetKeyCode() == WXK_DOWN || event.GetKeyCode() == WXK_PAGEDOWN)
		tree_->SetFocus();
	if(event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_HOME || event.GetKeyCode() == WXK_PAGEUP)
		symbolText_->SetFocus();
	if(event.GetKeyCode() == WXK_TAB){
		if(FindFocus() == symbolText_)
			memberText_->SetFocus();
		else
			symbolText_->SetFocus();
	}
}

MainFrame::MainFrame()
: tree_(0)
, directoryWatch_(0)
{
    Create(0, ID_FRAME, wxT("VimKick"), wxDefaultPosition,
		   wxDefaultSize, wxCAPTION | wxSYSTEM_MENU | 
		   wxMAXIMIZE_BOX | wxMINIMIZE_BOX | wxCLOSE_BOX | wxRESIZE_BORDER);
	SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
	SetClientSize(300, 500);
    Centre();
#ifdef WIN32
	SetIcon(wxIcon("main", wxBITMAP_TYPE_ICO_RESOURCE));
#endif

    wxMenuBar *mb = new wxMenuBar;
    wxMenu *fileMenu = new wxMenu;
    mb->Append(fileMenu, wxT("&File"));
    {
        //fileMenu->Append(IDM_FILE_OPEN, wxT("&Open..."));
        fileMenu->Append(IDM_FILE_OPEN_DIRECTORY, wxT("Open &Folder..."));
        fileMenu->AppendSeparator();
		fileMenu->Append(IDM_FILE_PREFERENCES, wxT("&Preferences"));
        fileMenu->AppendSeparator();
        fileMenu->Append(IDM_FILE_EXIT, wxT("E&xit"));
    }

    wxMenu *helpMenu = new wxMenu;
    mb->Append(helpMenu, wxT("&Help"));
    {
        helpMenu->Append(IDM_HELP_ABOUT, wxT("About"));
    }

    SetMenuBar(mb);

    wxBoxSizer* sz = new wxBoxSizer(wxVERTICAL);
    SetSizer(sz);
	{
		wxBoxSizer* topSizer = new wxBoxSizer(wxHORIZONTAL);
		sz->Add(topSizer, 0, wxEXPAND | wxBOTTOM, 2);

		symbolText_ = new SymbolTextCtrl(this, ID_SYMBOL_TEXT);
		topSizer->Add(symbolText_, 3, wxEXPAND | wxRIGHT, 1);

		memberText_ = new SymbolTextCtrl(this, ID_MEMBER_TEXT);
		topSizer->Add(memberText_, 2, wxEXPAND);

		tree_ = new ClassTree(this, ID_CLASS_TREE);
		sz->Add(tree_, 1, wxEXPAND);
    }

	timer_.SetOwner(this);
	timer_.Start(100);
}

void MainFrame::onFileOpen(wxCommandEvent& event)
{
	tree_->rebuild();
	tagStorage.flushTags();
}


void MainFrame::onFileOpenDirectory(wxCommandEvent& event)
{
	wxDirDialog dialog(this, wxT("Select a project folder"), wxGetCwd(),
					   wxDD_DIR_MUST_EXIST | wxRESIZE_BORDER, wxDefaultPosition,
					   wxSize(500, 600));
	int result = dialog.ShowModal();
	if(result == wxID_OK){
		tagStorage.clear();
		tagStorage.addSource(new TagSourceDirectory(&tagStorage, dialog.GetPath()));
	}

	tree_->rebuild();
	tagStorage.flushTags();
}

void MainFrame::onFilePreferences(wxCommandEvent& event)
{
	Serializer ser(PreferencesMaster::the());
	wxPropertyDialog dialog(this, wxT("Preferences"), ser);
	dialog.ShowModal();
}

void MainFrame::onFileExit(wxCommandEvent& event)
{
    Close();
}

void MainFrame::onHelpAbout(wxCommandEvent& event) {
    wxMessageBox(wxT("VimKick ") wxT(VIM_KICK_VERSION)
				 wxT("\nCopyright (c) 2008 Eugene <admix> Andreeshchev")
				 wxT("\nOn the terms of GNU GPL 2.0 (see LICENSE.txt)")
				 wxT("\nhttp://code.google.com/p/vimkick"),
                 wxT("About VimKick"), wxOK | wxICON_INFORMATION);
}

void MainFrame::poll()
{
	if(tagStorage.pollSources()){
		tree_->update();
		tagStorage.flushTags();
	}
}

void MainFrame::onIdle(wxIdleEvent& event)
{
	poll();
}

void MainFrame::onTimer(wxTimerEvent& event)
{
	//poll();
}

void MainFrame::onClose(wxCloseEvent& event)
{
    Destroy();
}

void MainFrame::onSymbolTextChanged(wxCommandEvent& event)
{
	tree_->setFilter(symbolText_->GetValue().utf8_str(), memberText_->GetValue().utf8_str());
}

void MainFrame::onSymbolTextEnter(wxCommandEvent& event)
{
	 if(const Tag* tag = tree_->selectedTag())
		editorPreferences.executeEditor(*tag);
}

void MainFrame::onSymbolSelected(wxTreeEvent& event)
{
	if(const Tag* tag = tree_->selectedTag()){
		editorPreferences.executeEditor(*tag);
	}
}

void MainFrame::serialize(Archive& ar)
{
    ASSERT(tree_);

    //ar(*tree_, "tree");
}

void MainFrame::commandFocus()
{
	Show();
	Raise();
	symbolText_->SetFocus();
}

static const wxCmdLineEntryDesc cmdLineDesc[] =
{
	{ wxCMD_LINE_SWITCH, "f", "focus", "activate window and focus symbol field" },
//	{ wxCMD_LINE_PARAM,  0, 0, "input file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE },

	{ wxCMD_LINE_NONE }
};

class VimKickConnection : public wxConnection{
public:
	VimKickConnection(MainFrame* mainFrame)
	: mainFrame_(mainFrame)
	{
	}
	bool OnExecute(const wxString& topic, char* data, int size, wxIPCFormat format)
	{
		wxCmdLineParser parser(cmdLineDesc);
		parser.SetCmdLine(data);

		if(parser.Parse(true) == 0){
			if(parser.Found("focus")){
				mainFrame_->commandFocus();
			}
			return true;
		}
		return false;
	}
protected:
	MainFrame* mainFrame_;
};

class VimKickServer : public wxServer{
public:
	VimKickServer()
	: mainFrame_(0)
	{
	}

	virtual wxConnectionBase * OnAcceptConnection(const wxString& topic)
	{
		return new VimKickConnection(mainFrame_);
	}

	void setMainFrame(MainFrame* frame) { mainFrame_ = frame; }		
protected:
	MainFrame* mainFrame_;

};



class VimKickClient : public wxClient{
public:
    VimKickClient()
	{
	}
	wxConnectionBase* OnMakeConnection() { return new VimKickConnection(0); }
};

class VimKickApp : public wxApp{
public:
    bool OnInit();
    int OnRun();
    int OnExit();

    DECLARE_CLASS(VimKickApp)
private:
    MainFrame* frame_;
	VimKickServer server_;
};

bool VimKickApp::OnInit()
{
	frame_ = 0;
	SetAppName("VimKick");
	wxString service("63435");

	VimKickClient client;
	if(client.ValidHost("localhost")){
		wxLogNull noLog;
		VimKickConnection* connection = (VimKickConnection*)client.MakeConnection("localhost", service, "VimKick");
		if(connection){
			if(argc > 1){
				std::string fullCommandLine;
				for(int i = 1; i < argc; ++i){
					if(!fullCommandLine.empty())
						fullCommandLine += " ";
					fullCommandLine = "\"";
					fullCommandLine += argv[i];
					fullCommandLine += "\"";
					//connection->Execute(argv[0]);
				}
				connection->Execute(fullCommandLine.c_str());
			}
			else{
				connection->Execute("--focus");
			}
			return false;
		}
	}

	//wxLog::SetActiveTarget(oldLog);

	/*
	std::string socketPath = wxStandardPaths::Get().GetUserDataDir().utf8_str();
	socketPath += Files::PATH_SEPARATOR;
	socketPath += ".socket";
	*/
	if(!server_.Create(service)){
		wxMessageBox(wxString("Error: unable to start DDE server."), wxString("error"), wxOK | wxICON_ERROR);
		return false;
	}

	if(tagPreferences.ctagsExecutable().empty()){
		wxMessageBox(wxString("Error: unable to locate ctags executable."), wxString("error"), wxOK | wxICON_ERROR);
		return false;
	}

	try{
		std::string preferencesPath = wxStandardPaths::Get().GetUserDataDir().utf8_str();
		preferencesPath += Files::PATH_SEPARATOR;
		preferencesPath += "preferences.ta";
		PreferencesMaster::the().setFileName(preferencesPath.c_str());
		void setFileName(const char* fileName);

		frame_ = new MainFrame;
		server_.setMainFrame(frame_);
		frame_->Show();
		SetTopWindow(frame_);

	}
	catch(ErrorGeneric& err){
		handleException(err);
		return false;
	}
	return true;
}


int VimKickApp::OnRun()
{
    try{

		if(frame_){
			wxIdleEvent event;
			event.SetEventObject(frame_);
			frame_->AddPendingEvent(event);
		}

        return wxApp::OnRun();
    }
    catch(ErrorGeneric& err){
        handleException(err);
        return -1;
    }
    return 0;
}

int VimKickApp::OnExit()
{
	PreferencesMaster::the().save();
    return wxApp::OnExit();
}

IMPLEMENT_CLASS(VimKickApp, wxApp)
IMPLEMENT_APP(VimKickApp)
